/*
 Developer: John Archer (john.archer.jpa@gmail.com)
 
 This file is part of VTBuilder.

 VTBuilder is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License.

 VTBuilder is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with VTBuilder.  If not, see <http://www.gnu.org/licenses/>.
 */
package core.tasks.pack;

import core.para.Para;
import core.rb.r.MR;
import gui.processor.Processor;
import gui.utils.ProgBar;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class MatchPack extends Thread {

    public ArrayList<MR>[] pOut;
    private final CountDownLatch stopLatch;
    private List<String> pIn;
    private HashMap<String, ArrayList<Integer>> lib;
    Random rand;
    private int refLen;
    private Processor p;

    public MatchPack(CountDownLatch stopLatch, List<String> pIn, HashMap<String, ArrayList<Integer>> l, int rLen, Processor p) {
        this.stopLatch = stopLatch;
        this.pIn = pIn;
        this.refLen = rLen;
        rand = new Random();
        this.lib = l;
        this.p = p;

    }

    public void run() {
        if (p == null) {
            pOut = match();
        } else {
            pOut = matchWithProg();
        }
        stopLatch.countDown();
    }

    private ArrayList<MR>[] match() {
        ArrayList<MR>[] mr = new ArrayList[refLen];
        String kmer, dna;
        TRHit[] siteHt;
        int mD,s,hts,df;
        ArrayList<TRHit>[] sd;
        HashMap<Integer, Integer> hsh = new HashMap<Integer, Integer>(250);
        for (int site = 0; site < mr.length; site++) {
            mr[site] = new ArrayList<MR>();
        }
        for (int x = 0; x < pIn.size(); x++) {
            siteHt = new TRHit[pIn.get(x).length() - 10];
            sd = new ArrayList[siteHt.length];
            for (int r = 0; r < sd.length; r++) {
                sd[r] = new ArrayList<TRHit>();
            }
            hsh.clear();
            for (int k = 0; k < siteHt.length; k++) {
                if (lib.containsKey(pIn.get(x).substring(k, k + 10))) {
                    kmer = pIn.get(x).substring(k, k + 10);
                    for (int t : lib.get(kmer)) {
                        sd[k].add(new TRHit(t, k, df = t - k));
                        if(hsh.get(df)==null){hsh.put(df, 1);}
                        else{hsh.put(df, hsh.get(df)+1);}
                    }
                }
            }
            ArrayList<Integer> mfreqs = new ArrayList<Integer>();
            ArrayList<Integer> mdiffs = new ArrayList<Integer>();
            mfreqs.add(0);
            mdiffs.add(0);
            for (Map.Entry<Integer, Integer> e : hsh.entrySet()) {
                if (e.getValue() > mfreqs.get(0)) {
                    //this is the highest - clear the rest of the list
                    mfreqs.clear();
                    mdiffs.clear();
                    mfreqs.add(e.getValue());
                    mdiffs.add(e.getKey());
                } else if (e.getValue() == mfreqs.get(0)) {
                    //equal so add it
                    mfreqs.add(e.getValue());
                    mdiffs.add(e.getKey());
                }
            }
            //if multiple hits that are the same take a random one
            //need to work out a random diff to use
            mD = mdiffs.get(0);
            if (mdiffs.size() > 1) {
                mD = mdiffs.get(rand.nextInt(mfreqs.size()));
            }
            for (int c = 0; c < sd.length; c++) {
                inner:
                for (int i = 0; sd[c] != null && i < sd[c].size(); i++) {
                    if (sd[c].get(i).df == mD) {
                        siteHt[c] = sd[c].get(i);
                        break inner;
                    }
                }
            }
            s = hts = 0;
            while (s < siteHt.length) {
                if (siteHt[s++] != null) {
                    hts++;
                }
            }
            if (hts > (int) (siteHt.length * 0.7)) {
                int swin = 0;
                while (siteHt[swin] == null) {
                    swin++;
                }
                dna = pIn.get(x).substring(siteHt[swin].rl);
                mr[siteHt[swin].tl].add(new MR(pIn.get(x).substring(siteHt[swin].rl), siteHt[swin].tl, siteHt[swin].tl + dna.length()));
            }
        }
        return mr;
    }
    
    
    private ArrayList<MR>[] matchWithProg() {
        ArrayList<MR>[] mr = new ArrayList[refLen];
        String kmer, dna;
        TRHit[] siteHt;
        int mD,s,hts,df;
        ArrayList<TRHit>[] sd;
        HashMap<Integer, Integer> hsh = new HashMap<Integer, Integer>(250);
        for (int site = 0; site < mr.length; site++) {mr[site] = new ArrayList<MR>();}
        
        DecimalFormat format = null;
        if(Para.GUI){p.dPrgA(ProgBar.START, pIn.size());}
        else{format = new DecimalFormat("##.##");}
        
        int tenPercentOfReads = (int)(pIn.size() * 0.1);
        
        for (int x = 0; x < pIn.size(); x++) {
            siteHt = new TRHit[pIn.get(x).length() - 10];
            sd = new ArrayList[siteHt.length];
            for (int r = 0; r < sd.length; r++) {
                sd[r] = new ArrayList<TRHit>();
            }
            hsh.clear();
            for (int k = 0; k < siteHt.length; k++) {
                if (lib.containsKey(pIn.get(x).substring(k, k + 10))) {
                    kmer = pIn.get(x).substring(k, k + 10);
                    for (int t : lib.get(kmer)) {
                        sd[k].add(new TRHit(t, k, df = t - k));
                        if (hsh.containsKey(df)) {
                            hsh.put(df, hsh.get(df) + 1);
                        } else {
                            hsh.put(df, 1);
                        }
                    }
                }
            }
            ArrayList<Integer> mfreqs = new ArrayList<Integer>();
            ArrayList<Integer> mdiffs = new ArrayList<Integer>();
            mfreqs.add(0);
            mdiffs.add(0);
            for (Map.Entry<Integer, Integer> e : hsh.entrySet()) {
                if (e.getValue() > mfreqs.get(0)) {
                    //this is the highest - clear the rest of the list
                    mfreqs.clear();
                    mdiffs.clear();
                    mfreqs.add(e.getValue());
                    mdiffs.add(e.getKey());
                } else if (e.getValue() == mfreqs.get(0)) {
                    //equal so add it
                    mfreqs.add(e.getValue());
                    mdiffs.add(e.getKey());
                }
            }
            //if multiple hits that are the same take a random one
            //need to work out a random diff to use
            mD = mdiffs.get(0);
            if (mdiffs.size() > 1) {
                mD = mdiffs.get(rand.nextInt(mfreqs.size()));
            }
            for (int c = 0; c < sd.length; c++) {
                inner:
                for (int i = 0; sd[c] != null && i < sd[c].size(); i++) {
                    if (sd[c].get(i).df == mD) {
                        siteHt[c] = sd[c].get(i);
                        break inner;
                    }
                }
            }
            s = hts = 0;
            while (s < siteHt.length) {
                if (siteHt[s++] != null) {
                    hts++;
                }
            }
            if (hts > (int) (siteHt.length * 0.7)) {
                int swin = 0;
                while (siteHt[swin] == null) {
                    swin++;
                }
                dna = pIn.get(x).substring(siteHt[swin].rl);
                mr[siteHt[swin].tl].add(new MR(dna, siteHt[swin].tl, siteHt[swin].tl + dna.length()));
            }
            if(Para.GUI){if(x%tenPercentOfReads==0){p.dPrgA(ProgBar.UPDATE, x);}}
            else{if(x%tenPercentOfReads==0){System.out.print("\r mapping: "+ format.format(((double)x/pIn.size()) * 100) + "%");}}
        }
        if(Para.GUI){p.dPrgA(ProgBar.END, 0);}
        else{System.out.println("\n");}
        return mr;
    }
}